﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Utilities.Windows.Interop
{
    #region Structures

    [StructLayout(LayoutKind.Sequential)]
    public struct WaveFormatEx
    {
        public short FormatTag; // 1
        public short Channels;
        public int SamplesPerSec;
        public int AvgBytesPerSec;
        public short BlockAlign;
        public short BitsPerSample;
        public short ExtraSize;

        public WaveFormatEx(short numChannels, int sampleRate, short bitsPerSample)
        {
            FormatTag = 1;
            Channels = numChannels;
            SamplesPerSec = sampleRate;
            BitsPerSample = bitsPerSample;
            AvgBytesPerSec = SamplesPerSec * Channels * BitsPerSample / 8;
            BlockAlign = (short)(Channels * BitsPerSample / 8);
            ExtraSize = 0;
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct WaveHeader
    {
        public IntPtr Data;
        public int BufferLength;
        public int BytesRecorded;
        public int User; // IntPtr
        public int Flags;
        public int Loops;
        public IntPtr Next;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct WaveOutCaps
    {
        public short ManufacturerId;
        public short ProductId;
        public int DriverVersion;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string ProductName;
        public WaveFormat Formats;
        public short Channels;
        public short Reserved;
        public WaveCaps Support;
    }

    public struct MMTime
    {
        public TimeFormat Type;
        public uint Value;
    }

    #endregion
    #region Enumerations

    public enum MMResult
    {
        #region MMSystem errors

        /// <summary>
        /// No error
        /// </summary>
        NoError = 0,
        /// <summary>
        /// Unspecified error
        /// </summary>
        Error,
        /// <summary>
        /// Device ID out of range
        /// </summary>
        BadDeviceId,
        /// <summary>
        /// Driver failed enable
        /// </summary>
        NotEnabled,
        /// <summary>
        /// Device already allocated
        /// </summary>
        Allocated,
        /// <summary>
        /// Device handle is invalid
        /// </summary>
        InvalidHandle,
        /// <summary>
        /// No device driver present
        /// </summary>
        NoDriver,
        /// <summary>
        /// Memory allocation error
        /// </summary>
        NoMemory,
        /// <summary>
        /// Function isn't supported
        /// </summary>
        NotSupported,
        /// <summary>
        /// Error value out of range
        /// </summary>
        BadErrNum,
        /// <summary>
        /// Invalid flag passed
        /// </summary>
        InvalidFlag,
        /// <summary>
        /// Invalid parameter passed
        /// </summary>
        InvalidParameter,
        /// <summary>
        /// Handle being used simultaneously on another thread (eg callback)
        /// </summary>
        HandleBusy,
        /// <summary>
        /// Specified alias not found
        /// </summary>
        InvalidAlias,
        /// <summary>
        /// Bad registry database
        /// </summary>
        BadDb,
        /// <summary>
        /// Registry key not found
        /// </summary>
        KeyNotFound,
        /// <summary>
        /// Registry read error
        /// </summary>
        ReadError,
        /// <summary>
        /// Registry write error
        /// </summary>
        WriteError,
        /// <summary>
        /// Registry delete error
        /// </summary>
        DeleteError,
        /// <summary>
        /// Registry value not found
        /// </summary>
        ValNotFound,
        /// <summary>
        /// Driver does not call DriverCallback
        /// </summary>
        NoDriverCb,
        /// <summary>
        /// More data to be returned
        /// </summary>
        MoreData,

        #endregion
        #region Wave errors

        /// <summary>
        /// Unsupported wave format
        /// </summary>
        BadFormat = 32,
        /// <summary>
        /// Still something playing
        /// </summary>
        StillPlaying,
        /// <summary>
        /// Header not prepared
        /// </summary>
        Unprepared,
        /// <summary>
        /// Device is synchronous
        /// </summary>
        Sync

        #endregion
    }

    [Flags]
    public enum WaveFormat
    {
        Mono11kHz8bit    = 0x00000001,
        Stereo11kHz8bit  = 0x00000002,
        Mono11kHz16bit   = 0x00000004,
        Stereo11kHz16bit = 0x00000008,
        Mono22kHz8bit    = 0x00000010,
        Stereo22kHz8bit  = 0x00000020,
        Mono22kHz16bit   = 0x00000040,
        Stereo22kHz16bit = 0x00000080,
        Mono44kHz8bit    = 0x00000100,
        Stereo44kHz8bit  = 0x00000200,
        Mono44kHz16bit   = 0x00000400,
        Stereo44kHz16bit = 0x00000800,
        Mono48kHz8bit    = 0x00001000,
        Stereo48kHz8bit  = 0x00002000,
        Mono48kHz16bit   = 0x00004000,
        Stereo48kHz16bit = 0x00008000,
        Mono96kHz8bit    = 0x00010000,
        Stereo96kHz8bit  = 0x00020000,
        Mono96kHz16bit   = 0x00040000,
        Stereo96kHz16bit = 0x00080000
    }

    [Flags]
    public enum WaveCaps
    {
        /// <summary>
        /// Supports pitch control
        /// </summary>
        WAVECAPS_PITCH          = 0x0001,
        /// <summary>
        /// Supports playback rate control
        /// </summary>
        WAVECAPS_PLAYBACKRATE   = 0x0002,
        /// <summary>
        /// Supports volume control
        /// </summary>
        WAVECAPS_VOLUME         = 0x0004,
        /// <summary>
        /// Separate left-right volume control
        /// </summary>
        WAVECAPS_LRVOLUME       = 0x0008,
        WAVECAPS_SYNC           = 0x0010,
        WAVECAPS_SAMPLEACCURATE = 0x0020
    }

    public enum TimeFormat
    {
        Millisecond = 0x01,
        Samples     = 0x02,
        Bytes       = 0x04,
        SMPTE       = 0x08,
        MIDI        = 0x10,
        Ticks       = 0x20,
    }

    public enum CallbackType
    {
        /// <summary>
        /// Callback type mask
        /// </summary>
        TypeMask = 0x00070000,
        /// <summary>
        /// No callback
        /// </summary>
        Null     = 0x00000000,
        /// <summary>
        /// Callback is a HWND
        /// </summary>
        Window   = 0x00010000,
        /// <summary>
        /// Callback is a HTASK
        /// </summary>
        Task     = 0x00020000,
        /// <summary>
        /// Callback is a FARPROC
        /// </summary>
        Function = 0x00030000,
        /// <summary>
        /// Thread ID replaces 16 bit task
        /// </summary>
        Thread   = 0x00020000,
        /// <summary>
        /// Callback is an EVENT Handle
        /// </summary>
        Event    = 0x00050000 
   }

    public enum WaveOutputMessage
    {
        /// <summary>
        /// Sent when the device is opened using the waveOutOpen function.
        /// </summary>
        Open  = 0x3BB,
        /// <summary>
        /// Sent when the device is closed using the waveOutClose function.
        /// </summary>
        Close = 0x3BC,
        /// <summary>
        /// Sent when the device driver is finished with a data block sent using the waveOutWrite function.
        /// </summary>
        Done  = 0x3BD
    }

    #endregion

    // TODO: Create fixed-point value type
    //   The high-order word contains the signed integer part of the number,
    //   and the low-order word contains the fractional part.
    //   A value of 0x8000 in the low-order word represents one-half,
    //   and 0x4000 represents one-quarter.
    //   For example, the value 0x00010000 specifies a value of 1.0,
    //   and a value of 0x000F8000 specifies a value of 15.5.
    public struct Fixed
    {
        private short integer;
        private ushort fractional;

        public static implicit operator float(Fixed value)
        {
            return value.integer + 0 * value.fractional;
        }
        public static implicit operator double(Fixed value)
        {
            return value.integer + 0 * value.fractional;
        }

        public static implicit operator Fixed(short value)
        {
            Fixed result = 0;
            result.integer = value;
            return result;
        }
        public static implicit operator Fixed(float value)
        {
            Fixed result = 0;
            return result;
        }
        public static implicit operator Fixed(double value)
        {
            Fixed result = 0;
            return result;
        }
    }

    public static class MMSystem
    {
        /// <summary>
        /// Callback function used with the waveform-audio output device. The waveOutProc function is a placeholder for the application-defined function name. The address of this function can be specified in the callback-address parameter of the waveOutOpen function.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio device associated with the callback.</param>
        /// <param name="msg">Waveform-audio output message.</param>
        /// <param name="instance">User-instance data specified with waveOutOpen.</param>
        /// <param name="param1">Message parameter.</param>
        /// <param name="param2">Message parameter.</param>
        public delegate void waveOutProc(IntPtr handle, WaveOutputMessage msg, int instance, int param1, int param2);

        /// <summary>
        /// Breaks a loop on the given waveform-audio output device and allows playback to continue with the next block in the driver list.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio output device.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutBreakLoop(IntPtr handle);

        /// <summary>
        /// Closes the given waveform-audio output device.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio output device. If the function succeeds, the handle is no longer valid after this call.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutClose(IntPtr handle);

        /// <summary>
        /// Retrieves the capabilities of a given waveform-audio output device.
        /// </summary>
        /// <param name="handle">Identifier of the waveform-audio output device. It can be either a device identifier or a handle of an open waveform-audio output device.</param>
        /// <param name="caps">Pointer to a WaveOutCaps structure to be filled with information about the capabilities of the device.</param>
        /// <param name="capsSize">Size, in bytes, of the WaveOutCaps structure.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutGetDevCaps(IntPtr handle, ref WaveOutCaps caps, int capsSize);

        /// <summary>
        /// Retrieves a textual description of the error identified by the given error number.
        /// </summary>
        /// <param name="error">Error number.</param>
        /// <param name="text">Pointer to a buffer to be filled with the textual error description.</param>
        /// <param name="textSize">Size, in characters, of the buffer pointed to by text.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutGetErrorText(MMResult error, StringBuilder text, int textSize);

        /// <summary>
        /// Retrieves the device identifier for the given waveform-audio output device.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio output device.</param>
        /// <param name="deviceId">Pointer to a variable to be filled with the device identifier.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutGetID(IntPtr handle, ref uint deviceId);

        /// <summary>
        /// Retrieves the number of waveform-audio output devices present in the system.
        /// </summary>
        /// <returns>Returns the number of devices. A return value of zero means that no devices are present or that an error occurred.</returns>
        [DllImport("winmm.dll")]
        public static extern int waveOutGetNumDevs();

        /// <summary>
        /// Retrieves the current pitch setting for the specified waveform-audio output device.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio output device.</param>
        /// <param name="pitch">Pointer to a variable to be filled with the current pitch multiplier setting. The pitch multiplier indicates the current change in pitch from the original authored setting. The pitch multiplier must be a positive value. The pitch multiplier is specified as a fixed-point value. The high-order word of the variable contains the signed integer part of the number, and the low-order word contains the fractional part. A value of 0x8000 in the low-order word represents one-half, and 0x4000 represents one-quarter. For example, the value 0x00010000 specifies a multiplier of 1.0 (no pitch change), and a value of 0x000F8000 specifies a multiplier of 15.5.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutGetPitch(IntPtr handle, ref int pitch);

        /// <summary>
        /// Retrieves the current playback rate for the specified waveform-audio output device.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio output device.</param>
        /// <param name="rate">Pointer to a variable to be filled with the current playback rate. The playback rate setting is a multiplier indicating the current change in playback rate from the original authored setting. The playback rate multiplier must be a positive value. The rate is specified as a fixed-point value. The high-order word of the variable contains the signed integer part of the number, and the low-order word contains the fractional part. A value of 0x8000 in the low-order word represents one-half, and 0x4000 represents one-quarter. For example, the value 0x00010000 specifies a multiplier of 1.0 (no playback rate change), and a value of 0x000F8000 specifies a multiplier of 15.5.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutGetPlaybackRate(IntPtr handle, ref int rate);

        /// <summary>
        /// Retrieves the current playback position of the given waveform-audio output device.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio output device.</param>
        /// <param name="time">Pointer to an MmTime structure.</param>
        /// <param name="timeSize">Size, in bytes, of the MmTime structure.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutGetPitch(IntPtr handle, ref MMTime time, int timeSize);

        /// <summary>
        /// Retrieves the current volume level of the specified waveform-audio output device.
        /// </summary>
        /// <param name="handle">Handle to an open waveform-audio output device. This parameter can also be a device identifier.</param>
        /// <param name="volume">Pointer to a variable to be filled with the current volume setting. The low-order word of this location contains the left-channel volume setting, and the high-order word contains the right-channel setting. A value of 0xFFFF represents full volume, and a value of 0x0000 is silence. If a device does not support both left and right volume control, the low-order word of the specified location contains the mono volume level. The full 16-bit setting(s) set with the waveOutSetVolume function is returned, regardless of whether the device supports the full 16 bits of volume-level control.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutGetVolume(IntPtr handle, ref int volume);

        /// <summary>
        /// Sends messages to the waveform-audio output device drivers.
        /// </summary>
        /// <param name="deviceId">Identifier of the waveform device that receives the message. If you supply a handle instead of a device ID, the function fails and returns the MMResult.NoSupport error code.</param>
        /// <param name="message">Message to send.</param>
        /// <param name="param1">Message parameter.</param>
        /// <param name="param2">Message parameter.</param>
        /// <returns>Returns the value returned from the driver.</returns>
        [DllImport("winmm.dll")]
        public static extern int waveOutMessage(uint deviceId, uint message, int param1, int param2);

        /// <summary>
        /// Opens the given waveform-audio output device for playback.
        /// </summary>
        /// <param name="handle">Pointer to a buffer that receives a handle identifying the open waveform-audio output device. Use the handle to identify the device when calling other waveform-audio output functions. This parameter might be null if the WAVE_FORMAT_QUERY flag is specified for flags.</param>
        /// <param name="deviceId">Identifier of the waveform-audio output device to open. It can be either a device identifier or a handle of an open waveform-audio input device. You can use the value 0x481 instead of a device identifier to automatically select an output device capable of playing the given format.</param>
        /// <param name="format">Pointer to a WaveFormatEx structure that identifies the format of the waveform-audio data to be sent to the device. You can free this structure immediately after passing it to waveOutOpen.</param>
        /// <param name="callback">Pointer to a fixed callback function, an event handle, a handle to a window, or the identifier of a thread to be called during waveform-audio playback to process messages related to the progress of the playback. If no callback function is required, this value can be zero. For more information on the callback function, see waveOutProc.</param>
        /// <param name="callbackInstance">User-instance data passed to the callback mechanism. This parameter is not used with the window callback mechanism.</param>
        /// <param name="flags">Flags for opening the device.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutOpen(ref IntPtr handle, uint deviceId, ref WaveFormatEx format, waveOutProc callback, IntPtr callbackInstance, CallbackType flags);

        /// <summary>
        /// Pauses playback on the given waveform-audio output device. The current position is saved. Use the waveOutRestart function to resume playback from the current position.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio output device.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutPause(IntPtr handle);

        /// <summary>
        /// Prepares a waveform-audio data block for playback.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio output device.</param>
        /// <param name="header">Pointer to a WaveHeader structure that identifies the data block to be prepared.</param>
        /// <param name="headerSize">Size, in bytes, of the WaveHeader structure.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutPrepareHeader(IntPtr handle, ref WaveHeader header, int headerSize);

        /// <summary>
        /// Stops playback on the given waveform-audio output device and resets the current position to zero. All pending playback buffers are marked as done and returned to the application.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio output device.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutReset(IntPtr handle);

        /// <summary>
        /// Resumes playback on a paused waveform-audio output device.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio output device.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutRestart(IntPtr handle);

        /// <summary>
        /// Sets the pitch for the specified waveform-audio output device.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio output device.</param>
        /// <param name="pitch">New pitch multiplier setting. This setting indicates the current change in pitch from the original authored setting. The pitch multiplier must be a positive value. The pitch multiplier is specified as a fixed-point value. The high-order word contains the signed integer part of the number, and the low-order word contains the fractional part. A value of 0x8000 in the low-order word represents one-half, and 0x4000 represents one-quarter. For example, the value 0x00010000 specifies a multiplier of 1.0 (no pitch change), and a value of 0x000F8000 specifies a multiplier of 15.5.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutSetPitch(IntPtr handle, int pitch);

        /// <summary>
        /// Sets the playback rate for the specified waveform-audio output device.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio output device.</param>
        /// <param name="rate">New playback rate setting. This setting is a multiplier indicating the current change in playback rate from the original authored setting. The playback rate multiplier must be a positive value. The rate is specified as a fixed-point value. The high-order word contains the signed integer part of the number, and the low-order word contains the fractional part. A value of 0x8000 in the low-order word represents one-half, and 0x4000 represents one-quarter. For example, the value 0x00010000 specifies a multiplier of 1.0 (no playback rate change), and a value of 0x000F8000 specifies a multiplier of 15.5.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutSetPlaybackRate(IntPtr handle, int rate);

        /// <summary>
        /// Sets the volume level of the specified waveform-audio output device.
        /// </summary>
        /// <param name="handle">Handle to an open waveform-audio output device. This parameter can also be a device identifier.</param>
        /// <param name="volume">New volume setting. The low-order word contains the left-channel volume setting, and the high-order word contains the right-channel setting. A value of 0xFFFF represents full volume, and a value of 0x0000 is silence. If a device does not support both left and right volume control, the low-order word of dwVolume specifies the volume level, and the high-order word is ignored.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutSetVolume(IntPtr handle, int volume);

        /// <summary>
        /// Cleans up the preparation performed by the waveOutPrepareHeader function. This function must be called after the device driver is finished with a data block. You must call this function before freeing the buffer.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio output device.</param>
        /// <param name="header">Pointer to a WaveHeader structure identifying the data block to be cleaned up.</param>
        /// <param name="headerSize">Size, in bytes, of the WaveHeader structure.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutUnprepareHeader(IntPtr handle, ref WaveHeader header, int headerSize);

        /// <summary>
        /// Sends a data block to the given waveform-audio output device.
        /// </summary>
        /// <param name="handle">Handle to the waveform-audio output device.</param>
        /// <param name="header">Pointer to a WaveHeader structure containing information about the data block.</param>
        /// <param name="headerSize">Size, in bytes, of the WaveHeader structure.</param>
        /// <returns>Returns MMResult.NoError if successful or an error otherwise.</returns>
        [DllImport("winmm.dll")]
        public static extern MMResult waveOutWrite(IntPtr handle, ref WaveHeader header, int headerSize);
    }
}
